/**************************************************************************************
*
*	Copyright (C) 2009 - 2012 Brent Andrew O. Chua - See LICENSE.txt for more info.
*		
*	Filename	-	ThreadWorker.cpp
*
*	Comments	-	See ThreadWorker.h
*
**************************************************************************************/
#include "../Include/ThreadWorker.h"
#include "../Include/TaskScheduler.h"
#include "../Include/ThreadTask.h"

namespace Pulse
{
	void WorkerThreadMain( void *pWorkerThread )
	{
		((ThreadWorker*)pWorkerThread)->ThreadProc();
	}

	ThreadWorker::ThreadWorker( void )
		: m_pScheduler( PSX_NULL )
	{
		
	}

	ThreadWorker::~ThreadWorker( void )
	{
	}

	EErrorCode::Type ThreadWorker::Initialize( TaskScheduler *pScheduler, const CHAR *pThreadName , BOOL bInitAsThisThread )
	{
		PSX_Assert( pScheduler, "Invalid pScheduler." );

		if ( m_pThread )
			return EErrorCode::OKAY; // Already initialized

		m_pScheduler = pScheduler;

		if ( bInitAsThisThread == FALSE)
			m_pThread = Thread::Create( pThreadName, WorkerThreadMain, (void*)this );
		else
			m_pThread = Thread::CreateThisThread( pThreadName );

		if ( m_pThread.IsNull() )
			return EErrorCode::_ERROR;

		return EErrorCode::OKAY;
	}

	void ThreadWorker::Join( void )
	{
		m_pThread->Join();
	}

	DWORD ThreadWorker::ThreadProc( void )
	{
		while ( TRUE )
		{
			Idle();

			DoWork();

			if ( m_pScheduler->IsShuttingDown() )
				break;
		};

		return 0;
	}

	void ThreadWorker::DoWork( TaskStatus *pToFinish )
	{
		do
		{
			TaskStatus *pStatus;
			ThreadTask *pTask;

			while ( (pTask = PopTask()) )
			{
				pStatus = pTask->GetTaskStatus();
				pTask->Run();
				pTask->GetTaskStatus()->SetDone();

				if( pToFinish && pToFinish->IsComplete() )
					return;
			};

		} while( StealTasks() );
	}

	void ThreadWorker::Idle( void )
	{
		m_pScheduler->SignalIdle();
		m_pScheduler->WaitUntilResume();
	}

	void ThreadWorker::PushTask( ThreadTask *pTask )
	{
		MutexLocker lock( m_taskMutex );

		m_tasks.Enqueue( pTask );
	}

	ThreadTask * ThreadWorker::PopTask( void )
	{
		MutexLocker lock( m_taskMutex );

		if ( m_tasks.GetSize() )
		{
			ThreadTask *pTask = m_tasks.Peek();
			ThreadTask *pPartialTask;
			
			// Attempt to partially pop task. Otherwise, pop the whole task.
			if ( pTask->PartialPop( &pPartialTask ) )
				return pPartialTask;
			else
				return m_tasks.Dequeue();
		}
		else
			return PSX_NULL;
	}

	BOOL ThreadWorker::StealTasks( void )
	{
		ThreadWorker *pWorker;
		SIZE_T numThreads = m_pScheduler->GetNumThreads();
		SIZE_T offset = PSX_Rand();

		for ( SIZE_T i = 0; i < numThreads; ++i )
		{
			pWorker = m_pScheduler->GetThreadWorker( (i + offset) % numThreads );

			if ( pWorker == this )
				continue;

			if ( pWorker->GiveUpSomeWork( this ) )
				return TRUE;

			if ( GetNumTasks() )
				return TRUE;
		}

		return FALSE;
	}

	BOOL ThreadWorker::GiveUpSomeWork( ThreadWorker *pIdleThread )
	{
		if ( GetNumTasks() == 0 )
			return FALSE;

		MutexLocker lock1( m_taskMutex );
		SIZE_T numTasks = GetNumTasks();

		if ( numTasks == 0 )
			return FALSE;

		// Attempt to split if we only have one task remaining
		if ( numTasks == 1 )
		{
			if ( m_tasks.Peek()->Split( pIdleThread ) )
				return TRUE;
		}

		SIZE_T numTasksToGrab = (numTasks + 1) / 2;
		{
			MutexLocker lock2( pIdleThread->m_taskMutex );
			for ( SIZE_T i = 0; i < numTasksToGrab; ++i )
				pIdleThread->m_tasks.Enqueue( m_tasks.Dequeue() );
		}

		return TRUE;
	}
}